package com.star.util.hibernate;

import java.io.Serializable;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import static org.hibernate.EntityMode.POJO;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.ArrayUtils;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.metadata.ClassMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.star.util.common.GenericUtils;
import com.star.util.common.MyBeanUtils;
import com.star.util.common.Page;
import com.star.util.common.SQL;
import com.star.util.common.Updater;
import com.star.util.json.JsonUtils;
import com.star.util.json.TargetStrategy;

public class HibernateGenericDao<T> extends HibernateDaoSupport {

	/**
	 * Entity class
	 */
	protected Class<T> entityClass;

	protected Logger logger;

	// ~ Constructor========================================================

	@SuppressWarnings("unchecked")
	protected HibernateGenericDao() {
		entityClass = GenericUtils.getSuperClassGenricType(getClass());
		logger = LoggerFactory.getLogger(entityClass);
	}

	// ~ Methods============================================================

	protected Class<T> getEntityClass() {
		Assert.notNull(entityClass);
		return entityClass;
	}

	/**
	 * ################### 获取 ######################
	 */
	public T get(Serializable id) {
		return get(id, false);
	}

	@SuppressWarnings("unchecked")
	public T get(Serializable id, boolean lock) {
		if (lock) {
			return (T) getSession().get(getEntityClass(), id, LockMode.UPGRADE);
		} else {
			return (T) getSession().get(getEntityClass(), id);
		}
	}

	public T load(Serializable id) {
		return load(id, false);
	}

	@SuppressWarnings("unchecked")
	public T load(Serializable id, boolean lock) {
		if (lock) {
			return (T) getSession()
					.load(getEntityClass(), id, LockMode.UPGRADE);
		} else {
			return (T) getSession().load(getEntityClass(), id);
		}
	}

	/**
	 * ################### 添加/更新 ######################
	 */
	public void save(T t) {
		getHibernateTemplate().save(t);
	}

	public void saveOrUpdate(T t) {
		getHibernateTemplate().saveOrUpdate(t);
	}

	public void merage(T t) {
		getHibernateTemplate().merge(t);
	}

	public void update(T t) {
		getHibernateTemplate().update(t);
	}

	@SuppressWarnings("unchecked")
	public void updateByUpdater(Updater updater) {
		ClassMetadata cm = getSessionFactory().getClassMetadata(entityClass);
		T bean = (T) updater.getBean();
		T po = (T) getSession().get(entityClass, cm.getIdentifier(bean, POJO));
		updaterCopyToPersistentObject(updater, po);
	}

	/**
	 * ################### 删除 ###################
	 */
	public void delete(T t) {
		getHibernateTemplate().delete(t);
	}

	public void deleteById(Serializable id) {
		getHibernateTemplate().delete(load(id));
	}

	/**
	 * use params to delete data
	 * 
	 * example:DELETE FROM Page WHERE sex=:sex AND age=:age AND name=:name
	 * 
	 * @param params
	 *            such as:<"age",17>
	 */
	public void deleteByParams(Map<Object, Object> params) {
		deleteByParams(params, SQL.EQUAL, SQL.AND);
	}

	/**
	 * use params to delete data
	 * 
	 * params must not be null
	 * 
	 * when params size =1 andOrOr can be null
	 * 
	 * example:DELETE FROM Page WHERE sex=:sex AND age=:age AND name=:name
	 * 
	 * @param params
	 *            such as <"age",17>
	 * @param eqOrNo
	 *            sql's Condition is "=" or "!="
	 * @param andOrOr
	 *            use "and" or "or" to Splice multi conditions
	 */
	public void deleteByParams(Map<Object, Object> params, String eqOrNo,
			String andOrOr) {
		Assert.notEmpty(params, "参数不能为空");
		StringBuffer sb = getHqlHead(SQL.DELETE);
		sb.append(getHql(params, eqOrNo, andOrOr));
		logger.debug("生成的sql为： " + sb);
		Session session = this.getSession();
		Query query = createNonPageSimpleQuery(session, sb.toString(), params);
		query.executeUpdate();
	}

	/**
	 * batch delete
	 * 
	 * example:DELETE FROM Page WHERE id in (1,2,3);
	 * 
	 * @param params
	 *            such as:<"age",17>
	 */
	public void batchDeleteByHql(Map<Object, Object> params) {
		batchDeleteByHql(params, SQL.EQUAL, SQL.AND);
	}

	/**
	 * batch delete
	 * 
	 * params must not be null
	 * 
	 * when params size =1 andOrOr can be null
	 * 
	 * @param params
	 *            such as:<"age",17>
	 * @param eqOrNo
	 *            sql's Condition is "=" or "!="
	 * @param andOrOr
	 *            use "and" or "or" to Splice multi conditions
	 */
	public void batchDeleteByHql(Map<Object, Object> params, String eqOrNo,
			String andOrOr) {
		Assert.notEmpty(params, "参数不能为空");
		StringBuffer sb = getHqlHead(SQL.DELETE);
		sb.append(getHql(params, eqOrNo, andOrOr));
		logger.debug("生成的sql为： " + sb);
		Session session = this.getSession();
		Query query = createBatchQuery(session, sb.toString(), params);
		query.executeUpdate();
	}

	/**
	 * ################# 统计 ###################
	 */
	@SuppressWarnings("unchecked")
	public long countByHql(String hql, Object... params) {
		Assert.isTrue(StringUtils.hasText(hql), "hql语句为空");
		Assert.notNull(params, "参数为空");
		List<Long> list = getHibernateTemplate().find(hql, params);
		if (list.size() > 0) {
			return list.get(0);
		}
		return 0;
	}

	@SuppressWarnings("unchecked")
	public long countByHql(String hql) {
		Assert.isTrue(StringUtils.hasText(hql), "hql语句为空");
		List<Long> list = getHibernateTemplate().find(hql);
		if (list.size() > 0) {
			return list.get(0);
		}
		return 0;
	}

	/**
	 * calc data's total,no condition
	 * 
	 * @return data's total
	 */
	public Long count() {
		return count(null, null, null);
	}

	/**
	 * calc data which match params total
	 * 
	 * @param params
	 *            such as:<"age",17>
	 * @return data's total
	 */
	public Long count(Map<Object, Object> params) {
		return count(params, SQL.EQUAL, SQL.AND);
	}

	/**
	 * calc data which match params total
	 * 
	 * when params size =1 andOrOr can be null
	 * 
	 * @param params
	 *            such as:<"age",17>
	 * @param eqOrNo
	 *            sql's Condition is "=" or "!="
	 * @param andOrOr
	 *            use "and" or "or" to Splice multi conditions
	 * @return
	 */
	public Long count(Map<Object, Object> params, String eqOrNo, String andOrOr) {
		StringBuffer sb = getHqlHead(SQL.COUNT);
		sb.append(getHql(params, eqOrNo, andOrOr));
		logger.debug("生成的sql为： " + sb);
		Session session = this.getSession();
		Query query = createNonPageSimpleQuery(session, sb.toString(), params);
		return (Long) query.uniqueResult();
	}

	/**
	 * ################# 查询 ###################
	 */
	/**
	 * select all data
	 * 
	 * @return
	 */
	public List<T> findAll() {
		return findAll(null, null, null, null);
	}

	/**
	 * select all data use order
	 * 
	 * @param order
	 *            order sort type such as:<age,DESC>;
	 * @return
	 */
	public List<T> findAll(Map<String, String> order) {
		return findAll(null, null, null, order);
	}

	/**
	 * select data which match params
	 * 
	 * @param params
	 *            Condition such as:<age,17>
	 * @return
	 */
	public List<T> findByParams(Map<Object, Object> params) {
		Assert.isTrue(!CollectionUtils.isEmpty(params), "参数不能为空");
		return findAll(params, SQL.EQUAL, SQL.AND, null);
	}

	/**
	 * select data which match params
	 * 
	 * @param params
	 *            Condition such as:<age,17>
	 * @param eqOrNo
	 *            sql's Condition is "=" or "!="
	 * @param andOrOr
	 *            use "and" or "or" to Splice multi conditions
	 * @return
	 */
	public List<T> findByParams(Map<Object, Object> params, String eqOrNo,
			String andOrOr) {
		Assert.isTrue(!CollectionUtils.isEmpty(params), "参数不能为空");
		return findAll(params, eqOrNo, andOrOr, null);
	}

	/**
	 * select data which match params
	 * 
	 * @param params
	 *            Condition such as:<age,17>
	 * @param eqOrNo
	 *            sql's Condition is "=" or "!="
	 * @param andOrOr
	 *            use "and" or "or" to Splice multi conditions
	 * @param order
	 *            order's field sort type such as:<age,DESC>;
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public List<T> findAll(Map<Object, Object> params, String eqOrNo,
			String andOrOr, Map<String, String> order) {
		StringBuffer sb = getHqlHead(SQL.FROM);
		if (!CollectionUtils.isEmpty(order)) {
			sb.append(getOrderHql(params, eqOrNo, andOrOr, order));
		}
		if (!CollectionUtils.isEmpty(params) && CollectionUtils.isEmpty(order)) {
			sb.append(getHql(params, eqOrNo, andOrOr));
		}
		logger.debug("生成的sql为： " + sb);
		Session session = this.getSession();
		Query query = createNonPageSimpleQuery(session, sb.toString(), params);
		return query.list();
	}

	/**
	 * custom hql
	 * 
	 * @param hql
	 * @param objects
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public List<T> findByHql(String hql, Object... objects) {
		Assert.isTrue(StringUtils.hasText(hql), "hql语句为空");
		Assert.notNull(objects, "参数为空");
		return getHibernateTemplate().find(hql, objects);
	}

	/**
	 * custom hql，select unique object
	 * 
	 * @param hql
	 * @param objects
	 * @return
	 */
	public Object getObjectByHql(String hql, Object... objects) {
		Assert.isTrue(StringUtils.hasText(hql), "hql语句为空");
		Assert.notNull(objects, "参数为空");
		Session session = this.getSession();
		Query query = createSimpleQuery(session, hql);
		for (int i = 0; i < objects.length; i++) {
			query.setParameter(i, objects[i]);
		}
		query.setMaxResults(1);
		return query.uniqueResult();
	}

	public Object getObjectByParams(Map<Object, Object> params) {
		Assert.notNull(params);
		StringBuffer sb = getHqlHead(SQL.FROM);
		sb.append(getHql(params));
		logger.debug("生成的sql： " + sb);
		Session session = this.getSession();
		Query query = createSimpleQuery(session, sb.toString());
		query.setMaxResults(1);
		return query.uniqueResult();
	}

	/**
	 * ################### 创建query，查询用################
	 */
	/**
	 * 创建query
	 * 
	 * @param session
	 * @param hql
	 * @param params
	 * @return query
	 */
	private Query createNonPageSimpleQuery(Session session, String hql,
			Map<Object, Object> params) {
		return createQuery(session, hql, params, null);
	}

	/**
	 * 创建query
	 * 
	 * @param session
	 * @param hql
	 * @return query
	 */
	private Query createSimpleQuery(Session session, String hql) {
		return createQuery(session, hql, null, null);
	}

	/**
	 * 创建query，多条件的还有分页
	 * 
	 * @param session
	 * @param hql
	 * @param params
	 * @param page
	 *            分页
	 * @return query
	 */
	private Query createQuery(Session session, String hql,
			Map<Object, Object> params, Page page) {
		Query query = session.createQuery(hql);
		if (!CollectionUtils.isEmpty(params)) {
			query.setProperties(params);
		}
		if (page != null) {
			query.setFirstResult(page.getBeginIndex())
					.setFetchSize(page.getPageSize())
					.setMaxResults(page.getPageSize());
		}
		return query;
	}

	/**
	 * 创建批处理的query，像批删除
	 * 
	 * @param session
	 * @param hql
	 * @param params
	 * @return
	 */
	@SuppressWarnings("rawtypes")
	private Query createBatchQuery(Session session, String hql,
			Map<Object, Object> params) {
		Query query = session.createQuery(hql);
		if (!CollectionUtils.isEmpty(params)) {
			Iterator iterator = params.entrySet().iterator();
			while (iterator.hasNext()) {
				String name = (String) ((Map.Entry) iterator.next()).getKey();
				List value = (List) params.get(name);
				query.setParameterList(name, value);
			}
		}
		return query;
	}

	/**
	 * ################# 分页 ###################
	 */
	@SuppressWarnings("unchecked")
	public Page findPage(String hql, int pageNum, int pageSize) {
		Page page = new Page();
		if (pageNum <= 0) {
			page.setPageNum(1);
		} else {
			page.setPageNum(pageNum);
		}
		if (pageSize <= 0) {
			page.setPageSize(Page.DEFAUTSIZE);
		} else {
			page.setPageSize(pageSize);
		}
		long total = countByHql(hql);
		if (total < 1) {
			page.setTotalCount((long) 0);
			page.setData(new ArrayList<T>(0));
			return page;
		}
		page.setTotalCount(total);
		Session session = this.getSession();
		Query query = createQuery(session, hql, null, page);
		List<T> datas = query.list();
		page.setData(datas);
		return page;
	}

	public String findFlexPage(String hql, int pageNum, int pageSize,
			String[] excludeFields) {
		Page page = findPage(hql, pageNum, pageSize);
		TargetStrategy ts = null;
		if (!ArrayUtils.isEmpty(excludeFields)) {
			ts = new TargetStrategy(entityClass);
			ts.setFields(excludeFields);
		}
		return JsonUtils.toJson(page, ts);
	}

	public Page findPage(String hql, int pageNum, Object... objects) {
		return findPage(hql, pageNum, Page.DEFAUTSIZE, objects);
	}

	public String findFlexPage(String hql, int pageNum, String[] excludeFields,
			Object... objects) {
		Page page = findPage(hql, pageNum, Page.DEFAUTSIZE, objects);
		TargetStrategy ts = null;
		if (!ArrayUtils.isEmpty(excludeFields)) {
			ts = new TargetStrategy(entityClass);
			ts.setFields(excludeFields);
		}
		return JsonUtils.toJson(page, ts);
	}

	public Page findPage(String hql, int pageNum) {
		return findPage(hql, pageNum, Page.DEFAUTSIZE);
	}

	public String findFlexPage(String hql, int pageNum, String[] excludeFields) {
		Page page = findPage(hql, pageNum, Page.DEFAUTSIZE);
		TargetStrategy ts = null;
		if (!ArrayUtils.isEmpty(excludeFields)) {
			ts = new TargetStrategy(entityClass);
			ts.setFields(excludeFields);
		}
		return JsonUtils.toJson(page, ts);
	}

	@SuppressWarnings("unchecked")
	public Page findPage(String hql, int pageNum, int pageSize,
			Object... objects) {
		Page page = new Page();
		if (pageNum <= 0) {
			page.setPageNum(1);
		} else {
			page.setPageNum(pageNum);
		}
		if (pageSize <= 0) {
			page.setPageSize(Page.DEFAUTSIZE);
		} else {
			page.setPageSize(pageSize);
		}
		long total = countByHql(hql, objects);
		if (total < 1) {
			page.setTotalCount((long) 0);
			page.setData(new ArrayList<T>(0));
			return page;
		}
		page.setTotalCount(total);
		Session session = this.getSession();
		Query query = createQuery(session, hql, null, page);
		for (int i = 0; i < objects.length; i++) {
			query.setParameter(i, objects[i]);
		}
		List<T> datas = query.list();
		page.setData(datas);
		return page;
	}

	/**
	 * ################# 更新或删除 ###################
	 */
	public void deleteOrUpdate(String hql, Object... objects) {
		getHibernateTemplate().bulkUpdate(hql, objects);
	}

	/**
	 * ################### 用map生成hql，用于比较复杂的hql拼接################
	 */
	/**
	 * 用带有字段名和值的一个map创建hql where开始的部分
	 * 
	 * @param params
	 *            字段名，值 如<age,17>
	 * @return
	 */
	private StringBuffer getHql(Map<Object, Object> params) {
		return getOrderHql(params, SQL.EQUAL, SQL.AND, null);
	}

	/**
	 * 用带有字段名和值的一个map创建hql where开始的部分
	 * 
	 * @param params
	 *            字段名，值 如<age,17>
	 * @param eqOrNo
	 *            是=还是!=
	 * @param andOrOr
	 *            多条件时，是and还是 or
	 * @return
	 */
	private StringBuffer getHql(Map<Object, Object> params, String eqOrNo,
			String andOrOr) {
		return getOrderHql(params, eqOrNo, andOrOr, null);
	}

	/**
	 * 用带有字段名和值的一个map创建hql where开始的部分，带排序的
	 * 
	 * @param params
	 *            字段名，值 如<age,17>
	 * @param eqOrNo
	 *            是=还是!=
	 * @param andOrOr
	 *            多条件时，是and还是 or
	 * @param order
	 *            排序的字段和排序方式如<age,DESC>
	 * @return
	 */
	@SuppressWarnings("rawtypes")
	private StringBuffer getOrderHql(Map<Object, Object> params, String eqOrNo,
			String andOrOr, Map<String, String> order) {
		StringBuffer sb = new StringBuffer();
		int i = 0;
		Iterator iterator;
		if (!CollectionUtils.isEmpty(params)) {
			Assert.isTrue(StringUtils.hasText(eqOrNo), "有参数时，必须要有=或!=");
			sb.append(" WHERE ");
			iterator = params.entrySet().iterator();
			while (iterator.hasNext()) {
				String name = (String) ((Map.Entry) iterator.next()).getKey();
				i++;
				sb.append(name);
				sb.append(eqOrNo);
				sb.append(":" + name);
				sb.append(" ");
				if (i != params.size()) {
					Assert.isTrue(StringUtils.hasText(andOrOr),
							"有参数时，必须要有and或or");
					sb.append(andOrOr);
					sb.append(" ");
				}
			}
		}
		if (!CollectionUtils.isEmpty(order)) {
			i = 0;
			sb.append(SQL.ORDER);
			sb.append(" ");
			iterator = order.entrySet().iterator();
			while (iterator.hasNext()) {
				String orderField = (String) ((Map.Entry) iterator.next())
						.getKey();
				String sort = order.get(orderField);
				i++;
				sb.append(orderField);
				sb.append(" ");
				sb.append(sort);
				if (i != order.size()) {
					sb.append(",");
				}
			}
		}
		return sb;
	}

	/**
	 * 将更新对象拷贝至实体对象，并处理many-to-one的更新。
	 * 
	 * @param updater
	 * @param po
	 */
	@SuppressWarnings("rawtypes")
	private void updaterCopyToPersistentObject(Updater updater, T po) {
		Map fieldValue = MyBeanUtils.describe(updater.getBean());
		Iterator iterator = fieldValue.entrySet().iterator();
		while (iterator.hasNext()) {
			String name = (String) ((Map.Entry) iterator.next()).getKey();
			Object value = fieldValue.get(name);
			if (!updater.isUpdate(name, value)) {
				continue;
			}
			if (value != null) {
				Class valueClass = value.getClass();
				ClassMetadata cm = getSessionFactory().getClassMetadata(
						valueClass);
				if (cm != null) {
					Serializable vid = cm.getIdentifier(value, POJO);
					// 如果更新的many to one的对象的id为空，则将many to one设置为null。

					if (vid != null) {
						value = getSession().load(valueClass, vid);
					} else {
						value = null;
					}
				}
				try {
					PropertyUtils.setProperty(po, name, value);
				} catch (Exception e) {
					// never

					logger.warn("更新对象时，拷贝属性异常", e);
				}
			}
		}
	}

	private StringBuffer getHqlHead(String operator) {
		StringBuffer sb = new StringBuffer();
		sb.append(operator);
		sb.append(entityClass.getSimpleName());
		return sb;
	}

}